package boot

import (
	"context"
	"fmt"
	"math/rand"
	"strings"
	"time"

	"dev.ixmxm.top/rms/config"
	"dev.ixmxm.top/rms/utils/log"
	"go.mongodb.org/mongo-driver/event"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

var LogChan = make(chan log.Entry, 2000)
var mongoClient *MongoDB

func init() {
	rand.Seed(time.Now().Unix())
}

//TryAddTx 尝试添加事物，如果不是TxContext，则直接生效
func TryAddTx(ctx context.Context, commit func(), rollback func()) {
	if funcTx, ok := ctx.(*TxContext); ok {
		funcTx.addTxFunc(commit, rollback)
		return
	}
	commit()
}

type txPair struct {
	commitF   func()
	rollBackF func()
}

type TxContext struct {
	context.Context
	mongoCtx context.Context
	txPars   []txPair
}

func (tx *TxContext) Value(key interface{}) interface{} {
	if tx.mongoCtx != nil {
		if v := tx.mongoCtx.Value(key); v != nil {
			return v
		}
	}
	return tx.Context.Value(key)
}

//Begin 开启事务
func Begin(ctx context.Context) *TxContext {
	return &TxContext{Context: ctx, txPars: make([]txPair, 0)}
}

//Commit 事务生效
func (tx *TxContext) Commit() {
	defer func() {
		tx.txPars = make([]txPair, 0)
	}()
	for _, txFuncs := range tx.txPars {
		txFuncs.commitF()
	}
}

//Rollback 事务取消
func (tx *TxContext) Rollback() {
	defer func() {
		tx.txPars = make([]txPair, 0)
	}()
	for _, txFuncs := range tx.txPars {
		txFuncs.rollBackF()
	}
}

func (tx *TxContext) addTxFunc(commitF func(), rollBackF func()) {
	tx.txPars = append(tx.txPars, txPair{commitF: commitF, rollBackF: rollBackF})
}

type MongoDB struct {
	client *mongo.Client
	db     *mongo.Database
}

func (mon *MongoDB) Collection(ctx context.Context, name string) *mongo.Collection {
	if funcTx, ok := ctx.(*TxContext); ok && funcTx.mongoCtx == nil {
		mongoTxCtx, commit, rollback := mon.begin(funcTx.Context)
		funcTx.addTxFunc(commit, rollback)
		funcTx.mongoCtx = mongoTxCtx
	}
	return mon.db.Collection(name)
}

func (mon *MongoDB) begin(ctx context.Context) (mongoCtx context.Context, commit func(), rollback func()) {
	session, errSession := mon.client.StartSession()
	if errSession != nil {
		log.Panicf("start mongodb session error:%s", errSession.Error())
		return nil, nil, nil
	}
	err := session.StartTransaction()
	if err != nil {
		log.Panicf("start mongo tx error:%s", err.Error())
	}
	mongoTxCtx := mongo.NewSessionContext(ctx, session)
	mongoCtx = mongoTxCtx

	commit = func() {
		defer session.EndSession(mongoTxCtx)
		if errCommit := mongoTxCtx.CommitTransaction(mongoTxCtx); errCommit != nil {
			log.Errorf("commit mongo tx error:%s", errCommit.Error())
		}
	}
	rollback = func() {
		defer session.EndSession(mongoTxCtx)
		if errRollback := mongoTxCtx.AbortTransaction(mongoTxCtx); errRollback != nil {
			log.Errorf("rollback mongo tx error:%s", errRollback.Error())
		}
	}
	return
}

func iniMongo() *MongoDB {
	cfg := config.Conf.MongoCfg
	address := strings.Join(cfg.Addresses, ",")
	uri := fmt.Sprintf("mongodb://%s:%s@%s/rms?replicaSet=%s", cfg.User, cfg.Password, address, cfg.ReplicaSet)
	cmdMonitor := &event.CommandMonitor{
		Started: func(ctx context.Context, evt *event.CommandStartedEvent) {
			// if evt.CommandName != "find" {
			// 	if strings.Contains(evt.Command.String(), "t_data_tasks") && !strings.Contains(evt.Command.String(), "t_data_tasks_index") {
			// 		log.InfofWithContext(ctx, evt.Command.String())
			// 	}
			// }
		},
	}
	// uri := fmt.Sprintf("mongodb://%s:%s@%s/rms?replicaSet=%s&connect=direct", cfg.User, cfg.Password, address, cfg.ReplicaSet)
	client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(uri).SetMonitor(cmdMonitor), options.Client().SetRetryWrites(true))
	if err != nil {
		panic("connect mongodb error:%s" + err.Error())
		return nil
	}
	db := client.Database("rms")
	mongoClient = &MongoDB{client: client, db: db}
	return mongoClient
}

func GetMongoClient() *MongoDB {
	if mongoClient != nil {
		return mongoClient
	}

	return iniMongo()
}
